import os
import tempfile
import threading
import time
import traceback
import uuid
from typing import List, Optional, Tuple

import speech_recognition as sr
import torch
import whisper
from pydub import AudioSegment
from tqdm import tqdm

from configs.global_config import global_config
from entity.speech_types import (
    AudioFileValidationResult,
    AudioSegmentResult,
    AudioTextExtractionResult,
)
from utils.logger_config import get_logger_config


class AudioProcessor:
    _instance = None
    _instance_lock = threading.Lock()
    """音频处理类，用于从音频文件中提取文字"""

    def __init__(self, language: str = "zh-CN", chunk_size: int = 30000) -> None:
        """
        初始化音频处理器

        参数:
            language: 语言代码，默认为中文
            chunk_size: 音频分块大小(毫秒)，用于处理较长的音频
        """
        self.recognizer = sr.Recognizer()
        self.chunk_size = chunk_size
        self.supported_formats: List[str] = [
            ".wav",
            ".mp3",
            ".flac",
            ".aiff",
            ".aif",
            ".m4a",
            "",
        ]
        self.logger = get_logger_config(name="audio_processor").get_logger()
        # 先初始化 _language，避免在设置属性时使用 logger
        self._language = None
        self.language = language
        self.model = self._load_whisper_model()
        self.logger.info(f"初始化音频处理器，语言: {language}")
        global_config.initialize()

    @classmethod
    def get_instance(cls, language: str = "zh-CN", chunk_size: int = 30000):
        if cls._instance is None:
            with cls._instance_lock:
                if cls._instance is None:
                    logger = get_logger_config(name="audio_processor").get_logger()
                    logger.info("创建 AudioProcessor 单实例...")
                    cls._instance = cls(language, chunk_size)
        return cls._instance

    def _load_whisper_model(self):
        """加载Whisper模型"""
        model_name = global_config.get("audio.whisper_model", "medium")
        local_model_path = global_config.get("audio.local_model_path", None)
        self.logger.info(f"正在加载Whisper模型: {model_name}...")
        try:
            # 检查是否配置了本地模型路径
            if local_model_path and os.path.exists(local_model_path):
                # 首先尝试构建完整的模型子目录路径
                model_subdir_path = os.path.join(local_model_path, model_name)
                
                if os.path.exists(model_subdir_path):
                    # 如果有对应模型名称的子目录，则使用该目录
                    self.logger.info(f"使用本地Whisper模型子目录: {model_subdir_path}")
                    model = whisper.load_model(model_subdir_path)
                else:
                    # 检查是否在本地路径下直接有对应的模型文件
                    model_file_extensions = ['.bin', '.pt', '.gguf']  # 常见模型文件扩展名
                    model_file_found = None
                    
                    # 检查是否有模型名称对应的具体文件
                    for ext in model_file_extensions:
                        potential_file = os.path.join(local_model_path, f"{model_name}{ext}")
                        if os.path.exists(potential_file):
                            model_file_found = potential_file
                            break
                    
                    if model_file_found:
                        # 如果找到了匹配的模型文件，使用该文件路径
                        self.logger.info(f"使用本地Whisper模型文件: {model_file_found}")
                        model = whisper.load_model(model_file_found)
                    else:
                        # 如果没有找到匹配的文件，尝试直接使用本地路径（可能包含模型文件）
                        self.logger.info(f"在本地路径查找Whisper模型: {local_model_path}")
                        model = whisper.load_model(local_model_path)
            else:
                self.logger.info(f"使用默认Whisper模型: {model_name} (将从网络下载)")
                model = whisper.load_model(model_name)
            self.logger.info(f"Whisper模型 {model_name} 加载成功")
            if torch.cuda.is_available():
                self.logger.info("GPU is available. Whisper will run on GPU.")
            else:
                self.logger.warning("GPU not available. Whisper will run on CPU.")
            return model
        except Exception as e:
            self.logger.error(f"加载Whisper模型 {model_name} 失败: {e}")
            self.logger.error(traceback.format_exc())
            raise

    @property
    def language(self) -> str:
        """获取当前识别的语言设置"""
        return self._language

    @language.setter
    def language(self, lang: str) -> None:
        """
        设置识别语言

        参数:
            lang: 语言代码，例如 "zh-CN" 表示中文，"en-US" 表示英文
        """
        if not isinstance(lang, str):
            raise ValueError("语言代码必须是字符串类型")
        self._language = lang
        self.logger.info(f"已更新识别语言为: {lang}")

    def _is_valid_audio_file(self, file_path: str) -> AudioFileValidationResult:
        """检查文件是否存在且是支持的音频格式"""
        if not os.path.exists(file_path):
            return AudioFileValidationResult(False, f"文件不存在: {file_path}")

        _, ext = os.path.splitext(file_path.lower())
        if ext not in self.supported_formats:
            return AudioFileValidationResult(
                False,
                f"不支持的音频格式: {ext}，支持的格式: {', '.join(self.supported_formats)}",
            )

        return AudioFileValidationResult(True)

    def _convert_to_wav(self, file_path: str) -> Optional[str]:
        """将音频文件转换为WAV格式"""
        _, ext = os.path.splitext(file_path.lower())

        if ext == ".wav":
            return file_path

        try:
            temp_wav = os.path.join(
                tempfile.gettempdir(),
                f"temp_{uuid.uuid4()}_{os.path.basename(file_path)}.wav",
            )
            self.logger.info(f"将 {file_path} 转换为WAV格式: {temp_wav}")
            audio = AudioSegment.from_file(file_path)
            audio.export(temp_wav, format="wav")
            return temp_wav
        except Exception as e:
            self.logger.error(f"转换音频格式失败: {str(e)}")
            self.logger.error(traceback.format_exc())
            return None

    def _split_audio(self, wav_path: str) -> List[AudioSegment]:
        """将长音频分割成小块"""
        try:
            audio = AudioSegment.from_wav(wav_path)
            chunks = [
                audio[i : i + self.chunk_size]
                for i in range(0, len(audio), self.chunk_size)
            ]
            self.logger.info(f"音频分割完成，共 {len(chunks)} 个块")
            return chunks
        except Exception as e:
            self.logger.error(f"分割音频失败: {str(e)}")
            self.logger.error(traceback.format_exc())
            return []

    def _recognize_chunk_with_whisper(
        self, audio_chunk: AudioSegment
    ) -> Tuple[str, Optional[str]]:
        """使用Whisper模型识别单个音频块的文字"""
        start_time = time.time()
        temp_file = ""
        try:
            temp_dir = tempfile.gettempdir()
            temp_file = os.path.join(temp_dir, f"temp_whisper_chunk_{uuid.uuid4()}.wav")
            audio_chunk.export(temp_file, format="wav")

            if not os.path.exists(temp_file):
                return "", "无法创建用于识别的临时文件"

            language = "zh" if self.language.startswith("zh") else None
            result = self.model.transcribe(
                temp_file,
                language=language,
                task="transcribe",
                fp16=torch.cuda.is_available(),
            )
            text = result["text"]

            if language == "zh":
                try:
                    from opencc import OpenCC

                    cc = OpenCC("t2s")
                    text = cc.convert(text)
                except ImportError:
                    self.logger.warning("未安装OpenCC，无法进行简繁转换")

            return text, None
        except Exception as e:
            self.logger.error(f"Whisper识别过程发生错误: {str(e)}")
            self.logger.error(traceback.format_exc())
            return "", f"Whisper识别过程发生错误: {str(e)}"
        finally:
            if temp_file and os.path.exists(temp_file):
                try:
                    os.remove(temp_file)
                except OSError as e:
                    self.logger.warning(f"删除临时文件失败: {str(e)}")
            elapsed_time = time.time() - start_time
            self.logger.info(f"音频块识别耗时: {elapsed_time:.2f} 秒")

    def extract_text_with_whisper(
        self, file_path: str, is_segment: bool = False
    ) -> AudioTextExtractionResult:
        """
        从音频文件中提取文字，使用Whisper模型
        """
        result = AudioTextExtractionResult(file_path)
        validation_result = self._is_valid_audio_file(file_path)
        if not validation_result.is_valid:
            result.error = validation_result.error
            return result

        wav_path = self._convert_to_wav(file_path)
        if not wav_path:
            result.error = "音频格式转换失败"
            return result

        should_delete_temp = wav_path != file_path

        try:
            audio = AudioSegment.from_wav(wav_path)
            total_duration = len(audio) / 1000

            if total_duration <= 60 or not is_segment:
                text, error = self._recognize_chunk_with_whisper(audio)
                result.segments.append(
                    AudioSegmentResult(0, 0, total_duration, text, error or "")
                )
                result.text = text
                if not error:
                    result.success = True
            else:
                chunks = self._split_audio(wav_path)
                all_texts = []
                current_pos = 0
                for i, chunk in enumerate(tqdm(chunks, desc="使用Whisper识别音频")):
                    chunk_duration = len(chunk) / 1000
                    text, error = self._recognize_chunk_with_whisper(chunk)
                    start_time = current_pos
                    end_time = start_time + chunk_duration
                    current_pos = end_time
                    result.segments.append(
                        AudioSegmentResult(i, start_time, end_time, text, error or "")
                    )
                    if text:
                        all_texts.append(text)
                result.text = " ".join(all_texts).strip()
                result.success = bool(result.text)

            if not result.success and not result.error:
                result.error = "未能从音频中提取到任何文字"

        except Exception as e:
            result.error = f"处理音频时发生未知错误: {str(e)}"
            self.logger.error(f"处理音频时发生未知错误: {traceback.format_exc()}")
        finally:
            if should_delete_temp and os.path.exists(wav_path):
                try:
                    os.remove(wav_path)
                except OSError as e:
                    self.logger.warning(f"删除临时WAV文件失败: {str(e)}")
        return result
